#ifndef SPLINE_H
#define SPLINE_H

#pragma once

#include "../ToolkitLayer.h"
#include <vector>
#include <stdexcept>


/**
 * This class implements a spline curve constructed by sub-division, for both open and closed curves. 
 * The algorithm used is the Dyn-Levin four point sub-division scheme (citation: 
 * N. Dyn, D. Levin, et al., A 4-point interpolatory scheme for curve design, Comput. Aided Geom. Design 4 (1987) 257-268. 
 * DOI: http://dx.doi.org/10.1016/0167-8396(87)90001-X ).
 *
 * It is adapted from the Spline class in the original Tabletop Framework by
 * T. Isenberg and J. Ales. The name of the functions were changed but their
 * contents were kept. Documentation comments were added as well.
 *
 * @author Tobias Isenberg - isenberg@cpsc.ucalgary.ca
 * @author Janez Ales - janez.ales@gmail.com
 * @author Fabricio Anastacio - fabriciocfa@yahoo.com
 * @author Petra Isenberg - petra.isenberg@ucalgary.ca
 * @author Matthew Tobiasz - tobiasz@ucalgary.ca
 * @since August 6, 2008 (adaptation)
 */
class TOOLKITLAYER_API Spline
{
	// Supporting structures
public:
	/** Defines a spline point type. */
	typedef struct {
		/** The x-coordinate of the point. */
		double x;
		/** The y-coordinate of the point. */
		double y;
	} SplinePoint;


	// Attributes
protected:
	/** The spline control points. */
	std::vector<SplinePoint> _controlPoints;
	
	/** The normal vectors at the control points. */
	std::vector<SplinePoint> _controlPointNormalVectors;
	
	/** The sample points along the spline. */
	std::vector<SplinePoint> _splinePoints;
	
	/** The normal vectors at the sample points. */
	std::vector<SplinePoint> _splinePointNormalVectors;
	
	/** The number of subdivisions of the spline. */
	unsigned long _level;
	
	/** The number of sample points. */
	unsigned long _numSplinePoints;
	
	/** Flag to indicate whether or not the spline is closed (@a true) or open (@a false). */
	bool _closed;

protected:
	/**
	 * Initializes the spline and sets the level.
	 *
	 * @param lev the number of subdivisions of the spline.
	 */
	void initialize(unsigned long lev);


	// Methods
public:
	/**
	 * Constructs an empty spline curve.
	 */
	Spline(const bool closed = true);
	Spline(unsigned long lev, const bool closed = true);
	/**
	 * Destroys this spline curve.
	 */
	~Spline();


	/**
	* Sets the level of the spline.
	*/
	void setLevel(unsigned long lev);
	
	/**
	 * Set whether or not the spline is closed (@a true) or open (@a false). 
	 *
	 * @param closedSpline the new value of #closed. 
	 */
	inline void setClosed(const bool closed){ _closed = closed; return; }
	
	/**
	 * Evaluates this spline using the current control points. It calculates
	 * sample points along the spline and the normal vectors at these points.
	 */
	void evaluate();

	/**
	 * Copies this spline to a new object and returns it.
	 *
	 * @return a copy of this spline.
	 */
	Spline copy(void);

	
	// ACCESSOR METHODS
	/**
	 * Returns the number of subdivisions of this spline.
	 *
	 * @return the number of subdivisions of this spline.
	 */
	unsigned long getLevel();
	
	/**
	 * Returns the number of control points of this spline.
	 *
	 * @return the number of control points of this spline.
	 */
	unsigned long getNumControlPoints();
	
	/**
	 * Returns the number of sample points of this spline.
	 *
	 * @return the number of sample points of this spline.
	 */
	unsigned long getNumSplinePoints();

	/** 
	 * Returns whether this is a closed (@a true) or open (@a false) spline.
	 *
	 * @return Whether this is a closed (@a true) or open (@a false) spline.
	 */
	inline bool isClosed(){ return _closed; }

	/**
	 * Returns the control points of this spline.
	 *
	 * @return the control points of this spline.
	 */
	std::vector<Spline::SplinePoint>* getControlPoints();
	
	/**
	 * Returns the normal vectors at the control points of this spline.
	 *
	 * @return the normal vectors at the control points of this spline.
	 */
	std::vector<Spline::SplinePoint>* getControlPointNormals();
	
	/**
	 * Returns the sample points of this spline.
	 *
	 * @return the sample points of this spline.
	 */
	std::vector<Spline::SplinePoint>* getSplinePoints();
	
	/**
	 * Returns the normal vectors at the sample points of this spline.
	 *
	 * @return the normal vectors at the sample points of this spline.
	 */
	std::vector<Spline::SplinePoint>* getSplinePointNormals();

	/**
	 * Adds a control point to the spline's list of control points.
	 *
	 * @param x the x-coordinate of the control point
	 * @param y the y-coordinate of the control point
	 */
	void addControlPoint(double x, double y);
	
	/**
	 * Modifies an existing spline control point.
	 *
	 * @param index The position of the existing control point. If @a index is out of bounds, this method does nothing.
	 * @param x The x-coordinate of the control point.
	 * @param y The y-coordinate of the control point.
	 */	
	void modifyControlPoint(const size_t index, const double x, const double y);

	/**
	 * Modifies an existing spline control point.
	 *
	 * @param index The position of the existing control point. If @a index is out of bounds, this method does nothing.
	 * @param point The new spline control point.
	 */	
	void modifyControlPoint(const size_t index, Spline::SplinePoint* const point);


	/**
		Evaluate position @a t along a linear Bézier curve, using the first and the last control points from #_controlPoints. 
		This is a helper method for drawing a straight line between the first and last control points.
		
		@param t The relative position along the linear Bézier curve, where @a t is between 0.0 and 1.0 inclusive.
		@param point The resulting position of the point. Instead dynamically allocating memory for a new Spline::SplinePoint object in this method, we use the 
			Return Value Optimization (RVO) and move the method return value to parameter value.
		@exception std::invalid_argument If @a t is outside of the range 0 to 1 inclusive, an exception is thrown.
		@exception std::logic_error If #_controlPoints has less then two elements, an exception is throw.
	*/
	inline void getLinearBezierCurvePoint(const double t, Spline::SplinePoint* point) throw (std::invalid_argument, std::logic_error){
	
		if(t < 0.0 || t > 1.0){
			throw std::invalid_argument("ERROR in Spline::getLinearBezierCurvePoint() - Parameter t must be between the range of [0.0, 1.0].");
		}else if(_controlPoints.size() < 2){
			throw std::logic_error("ERROR in Spline::getLinearBezierCurvePoint() - Vector of control points has less then two elements. Linear Bezier curve cannot be evaluated.");
		}
		
		point->x = (_controlPoints.front().x * (1.0 - t)) + (_controlPoints.back().x * t);
		point->y = (_controlPoints.front().y * (1.0 - t)) + (_controlPoints.back().y * t);
		
		return;
	}
	

	// DEBUGGING
	/**
	 * Prints the control points of this spline.
	 */
	void printControlPoints();
	
	/**
	 * Prints the normal vector at the control points of this spline.
	 */
	void printControlPointNormals();
	
	/**
	 * Prints the sample points of this spline.
	 */
	void printSplinePoints();
	
	/**
	 * Prints the normal vector at the sample points of this spline.
	 */
	void printSplinePointNormals();
	
};
#endif // SPLINE_H
